//
//  File.swift
//  SearchHeader
//
//  Created by 万孟超 on 2023/10/12.
//

import Cocoa

struct File {
    
    private static let fileExtension = ["h", "m", "mm", "pch", "swift"]
    private static let classCheckFileExtension = ["h", "mm", "m", "pch", "storyboard", "xib", "swift"]
    
    static func finder(_ file: Bool = false,
                       `dictionary`: Bool = true,
                       mutiSelect: Bool = false,
                       singleSelectBlock: ((String) -> ())? = nil,
                       mutiSelectBlock: (([String]) -> ())? = nil ) {
        let dialog = NSOpenPanel()
        dialog.canChooseFiles = file
        dialog.canChooseDirectories = dictionary
        dialog.allowsMultipleSelection = mutiSelect
        
        if dialog.runModal() == NSApplication.ModalResponse.OK {
            if mutiSelect {
                let results = dialog.urls
                var paths: [String] = []
                for result in results {
                    let path = result.path
                    paths.append(path)
                }
                mutiSelectBlock?(paths)
            }else{
                let result = dialog.url
                if let selectedFolder = result {
                    singleSelectBlock?(selectedFolder.path)
                }
            }
        }
    }
    
    static func obtainXcodeProjFilePath(_ projectPath: String, isDevelopmentProject: Bool = false) -> String {
        let enumerator = FileManager.default.enumerator(atPath: projectPath)
        while let file = enumerator?.nextObject() as? String {
            let filePath = projectPath + "/" + file
            if isDevelopmentProject {
                if file.contains("Pods/Pods.xcodeproj") {
                    return filePath
                }
            }else {
                if file.contains(".xcodeproj") && !file.contains("Pods/Pods.xcodeproj") && !file.contains("_Pods.xcodeproj") {
                    return filePath
                }else{
                    enumerator?.skipDescendants()
                }
            }
        }
        return ""
    }
    
    static func exportFile(_ fileContent: String) {
        let savePanel = NSSavePanel()

        savePanel.nameFieldStringValue = "HeaderSearchMap.txt"
        if savePanel.runModal() == NSApplication.ModalResponse.OK {
            if let url = savePanel.url {
                do {
                    try fileContent.write(to: url, atomically: true, encoding: .utf8)
                    Alert.showAlert("导出成功", message: "导出路径为：" + url.absoluteString, sureTitle: "确定")
                } catch {
                    Alert.showAlert("导出失败", message: "我也不知道为啥", sureTitle: "确定")
                }
            }
        }
    }
}

extension File {
    
    static func findReferencedFiles(projectPath: String) -> [String] {
        var usedClalssPathArray: [String] = []
        let projectURL = URL(fileURLWithPath: projectPath)
        var projectDirPath = projectPath.components(separatedBy: "/")
        projectDirPath.removeLast()
        let projectPathStr = projectDirPath.joined(separator: "/")
        let projectFileURL = projectURL.appendingPathComponent("project.pbxproj")
        
        if let pbxprojDic = try? NSDictionary(contentsOf: projectFileURL, error: ()) {
            let xcodePbxObjects = pbxprojDic.value(forKey: "objects") as? NSDictionary
            if let rootObjectKey = pbxprojDic.value(forKey: "rootObject") as? String {
                let mainGroup = xcodePbxObjects?.value(forKey: rootObjectKey) as? NSDictionary
                if let uuidMainGroup = mainGroup?.value(forKey: "mainGroup") as? String {
                    if let PBXGroupDic = xcodePbxObjects?.value(forKey: uuidMainGroup) as? NSDictionary {
                        obtainClassPath(projectPathStr, rootPath: projectPathStr, xcodePbxObjects: xcodePbxObjects!, pbxGroupDic: PBXGroupDic, uuid: uuidMainGroup, resultArray: &usedClalssPathArray)
                    }
                }
            }
        }
        return usedClalssPathArray
    }
    
    private static func obtainClassPath(_ classPath: String, rootPath: String, xcodePbxObjects: NSDictionary, pbxGroupDic: NSDictionary, uuid: String, resultArray: inout [String]) {
        if classPath.contains("Target Support Files") {
            return
        }
        let children = pbxGroupDic.value(forKey: "children") as? [String] ?? []
        let path = pbxGroupDic.value(forKey: "path") as? String ?? ""
        let sourceTree = pbxGroupDic.value(forKey: "sourceTree") as? String ?? ""
        var otherClassPath: String = classPath
        if !path.isEmpty {
            if sourceTree == "<group>" {
                otherClassPath = otherClassPath + "/" + path
            }else if sourceTree == "SOURCE_ROOT" {
                otherClassPath = rootPath + "/" + path
            }
        }
        if children.count == 0 {
            if otherClassPath.pathExtension.isEqualTo(fileExtension) {
                resultArray.append(otherClassPath.simplePath)
            }
        }else{
            for childrenUUid in children {
                if let childrenDic = xcodePbxObjects.value(forKey: childrenUUid) as? NSDictionary {
                  obtainClassPath(otherClassPath, rootPath: rootPath, xcodePbxObjects: xcodePbxObjects, pbxGroupDic: childrenDic, uuid: childrenUUid, resultArray: &resultArray)
                }
            }
        }
    }
    
    static func obtainPodsFilePath(_ projectPath: String) -> String {
        let enumerator = FileManager.default.enumerator(atPath: projectPath)
        while let file = enumerator?.nextObject() as? String {
            let filePath = projectPath + "/" + file
            var isDirectory: ObjCBool = false
            if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory), isDirectory.boolValue {
                if file.contains("Pods") {
                    return filePath
                }
            }
        }
        return ""
    }
    
    static func obtainPodsFileMap(_ podsFilePath: String) -> ([String], [String : String]) {
        var podsListDataSource: [String] = []
        var fileMap: [String : String] = [:]
        let podsPathStr = podsFilePath //+ "/Headers/Public"
        let enumerator = FileManager.default.enumerator(atPath: podsPathStr)
        while let file = enumerator?.nextObject() as? String {
            
            let filePath = podsPathStr + "/" + file
            var isDirectory: ObjCBool = false
            if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory), isDirectory.boolValue {
                if file == "Headers" {
                    enumerator?.skipDescendants()
                }
                continue
            }
            
            if file.hasSuffix(".h") {
                let filePathComponents = file.components(separatedBy: "/")
                let frameworkName = filePathComponents.first!
                let fileName = filePathComponents.last!
                fileMap[fileName] = frameworkName
                podsListDataSource.appendElement(frameworkName)
            }
        }
        return (podsListDataSource, fileMap)
    }
    
    static func replaceFileContent(_ filePath: String, line: Int, replace: String, alert: Bool = true) {
        do {
            // 读取文件 A 的内容
            let fileURL = URL(fileURLWithPath: filePath) // 替换为文件 A 的实际路径
            let fileContents = try String(contentsOf: fileURL)
            
            // 将文件内容按行分割成数组
            var lines = fileContents.components(separatedBy: .newlines)
            
            // 检查行号是否有效
            if line >= 1 && line <= lines.count {
                // 替换指定行的内容
                lines[line - 1] = replace
                
                // 将修改后的内容合并为新的字符串
                let updatedFileContents = lines.joined(separator: "\n")
                
                // 保存修改后的内容回文件 A
                try updatedFileContents.write(to: fileURL, atomically: true, encoding: .utf8)
                if alert {
                    Alert.showAlert(message: "替换成功" + filePath, sureTitle: "确认")
                }
            } else {
                Alert.showAlert(message: "出错了，手动替换一下" + filePath, sureTitle: "确认")
            }
        }catch {
            Alert.showAlert(message: "出错了，手动替换一下" + filePath, sureTitle: "确认")
        }
    }
}

extension File {
    
    enum AllClassCheckType {
        case all
        case getOne
        case storyboard
    }
    static func classCheckFindReferencedFiles(projectPath: String, progressBlock: ((String) -> ())? = nil) -> [String : [String : [String]]] {
        var allClassDic: [String : [String : [String]]] = [:]
        let projectURL = URL(fileURLWithPath: projectPath)
        var projectDirPath = projectPath.components(separatedBy: "/")
        projectDirPath.removeLast()
        let projectPathStr = projectDirPath.joined(separator: "/")
        let projectFileURL = projectURL.appendingPathComponent("project.pbxproj")
        
        if let pbxprojDic = try? NSDictionary(contentsOf: projectFileURL, error: ()) {
            let xcodePbxObjects = pbxprojDic.value(forKey: "objects") as? NSDictionary
            if let rootObjectKey = pbxprojDic.value(forKey: "rootObject") as? String {
                let mainGroup = xcodePbxObjects?.value(forKey: rootObjectKey) as? NSDictionary
                if let uuidMainGroup = mainGroup?.value(forKey: "mainGroup") as? String {
                    if let PBXGroupDic = xcodePbxObjects?.value(forKey: uuidMainGroup) as? NSDictionary {
                        obtainAllClassPath(projectPathStr, rootPath: projectPathStr, xcodePbxObjects: xcodePbxObjects!, pbxGroupDic: PBXGroupDic, uuid: uuidMainGroup, resultDic: &allClassDic, type: .all, progressBlock: progressBlock)
                        obtainAllClassPath(projectPathStr, rootPath: projectPathStr, xcodePbxObjects: xcodePbxObjects!, pbxGroupDic: PBXGroupDic, uuid: uuidMainGroup, resultDic: &allClassDic, type: .getOne, progressBlock: progressBlock)
                        obtainAllClassPath(projectPathStr, rootPath: projectPathStr, xcodePbxObjects: xcodePbxObjects!, pbxGroupDic: PBXGroupDic, uuid: uuidMainGroup, resultDic: &allClassDic, type: .storyboard, progressBlock: progressBlock)
                    }
                }
            }
        }
        return allClassDic
    }
    
    private static func obtainAllClassPath(_ classPath: String, rootPath: String, xcodePbxObjects: NSDictionary, pbxGroupDic: NSDictionary, uuid: String, resultDic: inout [String : [String : [String]]], type: AllClassCheckType, progressBlock: ((String) -> ())? = nil) {
        if classPath.contains("Target Support Files") {
            return
        }
        let children = pbxGroupDic.value(forKey: "children") as? [String] ?? []
        let path = pbxGroupDic.value(forKey: "path") as? String ?? ""
        let sourceTree = pbxGroupDic.value(forKey: "sourceTree") as? String ?? ""
        var otherClassPath: String = classPath.simplePath
        if !path.isEmpty {
            if sourceTree == "<group>" {
                otherClassPath = otherClassPath + "/" + path
            }else if sourceTree == "SOURCE_ROOT" {
                otherClassPath = rootPath + "/" + path
            }
        }
        if children.count == 0 {
            if otherClassPath.pathExtension.isEqualTo(classCheckFileExtension) && !otherClassPath.contains("Pods") {
                let fileName = otherClassPath.components(separatedBy: "/").last!.components(separatedBy: ".").first!
                switch type {
                    case .all:
                        progressBlock?(fileName)
                        var fileInfo = resultDic[fileName]
                        if fileInfo == nil {
                            fileInfo = [:]
                            fileInfo!["paths"] = []
                            fileInfo!["keys"] = []
                        }
                        
                        fileInfo!["paths"]!.append(otherClassPath)
                        fileInfo!["keys"]!.append(uuid)
                        resultDic[fileName] = fileInfo
                    case .getOne:
                        do {
                            let content = try String(contentsOfFile: otherClassPath, encoding: .utf8)
                            let contentFileString = content.filterOutSpecialChar
                            if content.isEmpty { return }
                            let conentFileArray = contentFileString.components(separatedBy: " ")
                            var fileAllKeys: [String : String] = [:]
                            for subString in conentFileArray {
                                if !subString.isEmpty {
                                    fileAllKeys[subString] = "0"
                                }
                            }
                            if otherClassPath.pathExtension == "pch" {
                                for key in resultDic.keys {
                                    if let _ = fileAllKeys[key] {
                                        fileAllKeys[key] = "1"
                                    }
                                }
                            }else if otherClassPath.pathExtension == "swift" && fileName.contains("+") {
                                resultDic.removeValue(forKey: fileName)
                            }else{
                                for key in resultDic.keys {
                                    if key == fileName { continue }
                                    if let _ = fileAllKeys[key] {
                                        fileAllKeys[key] = "1"
                                    }
                                }
                            }
                            for (key, value) in fileAllKeys {
                                if value == "1" {
                                    /// 用到了
                                    resultDic.removeValue(forKey: key)
                                }
                            }
                        }catch {
                            
                        }
                    case .storyboard:
                        
                        if otherClassPath.pathExtension == "storyboard" {
                            resultDic.removeValue(forKey: fileName)
                        }
                }
            }
        }else{
            for childrenUUid in children {
                if let childrenDic = xcodePbxObjects.value(forKey: childrenUUid) as? NSDictionary {
                    obtainAllClassPath(otherClassPath, rootPath: rootPath, xcodePbxObjects: xcodePbxObjects, pbxGroupDic: childrenDic, uuid: childrenUUid, resultDic: &resultDic, type: type, progressBlock: progressBlock)
                }
            }
        }
    }
}

extension File {
    
    static func findProjectReferencedFiles(projectPath: String) -> [String] {
        var usedClalssPathArray: [String] = []
        let projectURL = URL(fileURLWithPath: projectPath)
        var projectDirPath = projectPath.components(separatedBy: "/")
        projectDirPath.removeLast()
        let projectPathStr = projectDirPath.joined(separator: "/")
        let projectFileURL = projectURL.appendingPathComponent("project.pbxproj")
        
        if let pbxprojDic = try? NSDictionary(contentsOf: projectFileURL, error: ()) {
            let xcodePbxObjects = pbxprojDic.value(forKey: "objects") as? NSDictionary
            if let rootObjectKey = pbxprojDic.value(forKey: "rootObject") as? String {
                let mainGroup = xcodePbxObjects?.value(forKey: rootObjectKey) as? NSDictionary
                if let uuidMainGroup = mainGroup?.value(forKey: "mainGroup") as? String {
                    if let PBXGroupDic = xcodePbxObjects?.value(forKey: uuidMainGroup) as? NSDictionary {
                        classCleanObtainClassPath(projectPathStr, rootPath: projectPathStr, xcodePbxObjects: xcodePbxObjects!, pbxGroupDic: PBXGroupDic, uuid: uuidMainGroup, resultArray: &usedClalssPathArray)
                    }
                }
            }
        }
        return usedClalssPathArray
    }
    
    private static func classCleanObtainClassPath(_ classPath: String, rootPath: String, xcodePbxObjects: NSDictionary, pbxGroupDic: NSDictionary, uuid: String, resultArray: inout [String]) {
        if classPath.contains("Target Support Files") {
            return
        }
        let children = pbxGroupDic.value(forKey: "children") as? [String] ?? []
        let path = pbxGroupDic.value(forKey: "path") as? String ?? ""
        let sourceTree = pbxGroupDic.value(forKey: "sourceTree") as? String ?? ""
        var otherClassPath: String = classPath
        if !path.isEmpty {
            if sourceTree == "<group>" {
                otherClassPath = otherClassPath + "/" + path
            }else if sourceTree == "SOURCE_ROOT" {
                otherClassPath = rootPath + "/" + path
            }
        }
        if children.count == 0 {
            resultArray.append(otherClassPath.simplePath)
        }else{
            for childrenUUid in children {
                if let childrenDic = xcodePbxObjects.value(forKey: childrenUUid) as? NSDictionary {
                    classCleanObtainClassPath(otherClassPath, rootPath: rootPath, xcodePbxObjects: xcodePbxObjects, pbxGroupDic: childrenDic, uuid: childrenUUid, resultArray: &resultArray)
                }
            }
        }
    }
}

extension File {
    static func searchDerivedData(_ filePath: String, sizeBlock: @escaping (Double) -> (), floderBlock:@escaping (String, Double) -> (), finishBlock: @escaping () -> ()) {
        var fileArray: [String] = []
        var size: Double = 0
        sizeBlock(size)
        var index = 0
        var completeNum = 0
        let enumerator = FileManager.default.enumerator(atPath: filePath)
        while let file = enumerator?.nextObject() as? String {
            if !file.starts(with: ".") {
                index += 1
                fileArray.append(file)
            }
            enumerator?.skipDescendants()
        }
        if fileArray.count == 0 {
            finishBlock()
        }
        for fileName in fileArray {
            getActualSizeOfFolderTree(at: filePath + "/" + fileName) {
                completeNum += 1
                size += $0
                sizeBlock(size)
                floderBlock(fileName, $0)
                if completeNum == index {
                    finishBlock()
                }
            }
        }
    }
    
    static func getActualSizeOfFolderTree(at folder: String, completeBlock: @escaping (Double) -> ()) {
        Async(.custom(name: folder)) {
            var currentFileSize: Double = 0
            let fileManager = FileManager.default
            if let folderURL = URL(string: folder) {
                guard let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [.totalFileAllocatedSizeKey], options: [], errorHandler: nil) else {
                    completeBlock(0)
                    return
                }
                
                for case let fileURL as URL in enumerator {
                    do {
                        let resourceValues = try fileURL.resourceValues(forKeys: [.totalFileAllocatedSizeKey])
                        let size = Double(resourceValues.totalFileAllocatedSize ?? 0)
                        currentFileSize += size
                    } catch {
                        print("Error: \(error)")
                    }
                }
            }
            completeBlock(currentFileSize)
        }
    }
    
    static func requestFolderPermissions(_ filePath: String, successBlock: @escaping ( @escaping () -> () ) -> (), cancelBlock: @escaping () -> ()) {
        Async(.main) {
            if let bookmarkData = UserDefaults.standard.object(forKey: "FolderAccess") as? Data {
                var isStale = false
                guard let url = try? URL(resolvingBookmarkData: bookmarkData, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) else {
                    return
                }
                if !isStale {
                    // 开启访问权限
                    if url.startAccessingSecurityScopedResource() {
                        successBlock({
                            url.stopAccessingSecurityScopedResource()
                        })
                    }
                    
                }
            } else {
                let openPanel = NSOpenPanel()
                openPanel.canChooseDirectories = true
                openPanel.canChooseFiles = false
                openPanel.allowsMultipleSelection = false
                openPanel.directoryURL = URL(fileURLWithPath: filePath)
                
                openPanel.begin { (result) in
                    if result == .OK, let url = openPanel.urls.first {
                        let bookmarkData = try? url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
                        if bookmarkData != nil {
                            // 存储访问权限
                            UserDefaults.standard.set(bookmarkData, forKey: "FolderAccess")
                        }
                        successBlock({})
                    }else if result == .cancel {
                        cancelBlock()
                    }
                }
            }
        }
    }
}

extension File {
    static func removePath(_ filePath: String, removeBlock: @escaping (Bool) -> ()) {
        requestFolderPermissions(filePath) { finish in
            Async(.custom(name: filePath)) {
                try? FileManager.default.removeItem(atPath: filePath)
                if FileManager.default.fileExists(atPath: filePath) {
                    removeBlock(false)
                }else{
                    removeBlock(true)
                }
                finish()
            }
        } cancelBlock: {
            removeBlock(false)
        }
    }
}
